﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Reflection;

namespace DaveSexton.Labs
{
	[ContractClass(typeof(LabFactoryBaseContract<>))]
#if (SILVERLIGHT || NETFX_CORE) && !WINDOWS_PHONE
	public abstract class LabFactoryBase<TDiscoveryCatalog>
		where TDiscoveryCatalog : class
#else
	internal abstract class LabFactoryBase<TDiscoveryCatalog>
		where TDiscoveryCatalog : class
#endif
	{
		#region Public Properties
		#endregion

		#region Private / Protected
		protected LabCatalog Catalog
		{
			get
			{
				Contract.Ensures(Contract.Result<LabCatalog>() != null);

				return catalog;
			}
		}

		private readonly LabCatalog catalog;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="LabFactoryBase" /> class for derived classes.
		/// </summary>
		protected LabFactoryBase(LabCatalog catalog)
		{
			Contract.Requires(catalog != null);

			this.catalog = catalog;
		}
		#endregion

		#region Methods
		[ContractInvariantMethod]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
		private void ObjectInvariant()
		{
			Contract.Invariant(catalog != null);
		}

		[Pure]
		private static bool NotNullOrDisabled(ILab lab)
		{
			Contract.Ensures(!Contract.Result<bool>() || (lab != null));
			Contract.Ensures(!Contract.Result<bool>() || lab.IsEnabled);

			return lab != null && lab.IsEnabled;
		}

		public ICollection<Lab> LoadLabs(TDiscoveryCatalog discoveryCatalog)
		{
			Contract.Requires(discoveryCatalog != null);
			Contract.Ensures(Contract.Result<ICollection<Lab>>() != null);
			Contract.Ensures(Contract.Result<ICollection<Lab>>().IsReadOnly);
			////Contract.Ensures(Contract.ForAll(Contract.Result<ICollection<Lab>>(), lab => lab != null));
			////Contract.Ensures(Contract.ForAll(Contract.Result<ICollection<Lab>>(), lab => lab.IsEnabled));

			ICollection<Lab> collection = LoadLabsIterator(discoveryCatalog).ToList().AsReadOnly();

			Contract.Assume(collection.IsReadOnly);

			return collection;
		}

		private IEnumerable<Lab> LoadLabsIterator(TDiscoveryCatalog discoveryCatalog)
		{
			var priorityLabs = new List<Type>();

			if ((catalog.Activation & LabActivationStrategies.CatalogLabs) == LabActivationStrategies.CatalogLabs)
			{
				foreach (var lab in LoadLabs(catalog.PriorityLabs))
				{
					priorityLabs.Add(lab.ActualLab.GetType());

					yield return lab;
				}
			}

			if ((catalog.Activation & LabActivationStrategies.DiscoveredLabs) == LabActivationStrategies.DiscoveredLabs)
			{
				var importedLabs = ImportLabs(discoveryCatalog, priorityLabs.AsReadOnly());

				if (importedLabs != null)
				{
					foreach (var lab in LoadLabs(importedLabs))
					{
						yield return lab;
					}
				}
			}
		}

		protected abstract IEnumerable<ILab> ImportLabs(TDiscoveryCatalog discoveryCatalog, IList<Type> excludeLabs);

		private IEnumerable<Lab> LoadLabs(IEnumerable<ILab> labs)
		{
			Contract.Requires(labs != null);
			Contract.Ensures(Contract.Result<IEnumerable<Lab>>() != null);

			foreach (var lab in labs.Where(NotNullOrDisabled))
			{
				Contract.Assume(!lab.IsProxy);

				var strongTyped = lab as Lab ?? new ProxyLab(lab);

				Initialize(lab, strongTyped);

				yield return strongTyped;
			}
		}

		private void Initialize(ILab lab, Lab strongTypedLab)
		{
			Contract.Requires(lab != null);
			Contract.Requires(!lab.IsProxy);
			Contract.Requires(strongTypedLab != null);
			Contract.Requires(strongTypedLab.ActualLab == lab);

#if NETFX_CORE
			var assembly = lab.GetType().GetTypeInfo().Assembly;
#else
			var assembly = lab.GetType().Assembly;
#endif

			strongTypedLab.SourceCodeResourceManager = catalog.GetSourceCodeResourceManager(assembly);
			strongTypedLab.SourceXamlResourceManager = catalog.GetSourceXamlResourceManager(assembly);

			if (lab != strongTypedLab)
			{
				Contract.Assume(!lab.HasProxy);

				lab.Proxy = strongTypedLab;
			}
		}
		#endregion
	}

	[ContractClassFor(typeof(LabFactoryBase<>))]
	internal abstract class LabFactoryBaseContract<TDiscoveryCatalog> : LabFactoryBase<TDiscoveryCatalog>
		where TDiscoveryCatalog : class
	{
		private LabFactoryBaseContract(LabCatalog catalog)
			: base(catalog)
		{
		}

		protected override IEnumerable<ILab> ImportLabs(TDiscoveryCatalog discoveryCatalog, IList<Type> excludeLabs)
		{
			Contract.Requires(discoveryCatalog != null);
			Contract.Requires(excludeLabs != null);
			return null;
		}
	}
}